Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature suggestion: make callbacks configurable #280

Merged
merged 3 commits into from
Jun 14, 2024

Conversation

abradley2
Copy link
Contributor

I'm using huh to make some internal tooling with neat little TUI's.

In this case, we have suite of tools that are their own individual huh forms. These can be used as top level forms, but I've also set things up in a way where forms can compose other forms that are otherwise top level. This uses a pattern commonly referred to as Nested Tea. Some of our tools are sometimes the top level form, other times they're forms within a form and this pattern makes them super composable.

It's a bit tricky, to compose them in this way, however. It often involves a line that looks sort of like this:

	if m.subFormFoo != nil && m.subFormFoo.State == huh.StateCompleted {
		cmd = tea.Batch(cmd, getData(m))
		m.subFormFoo = nil
	}
       
	switch curmsg := msg.(type) {
	case getDataSuccess:
		m.subFormBar = NewFormBar()
                cmd = tea.Batch(cmd, subFormBar.Init())

The tricky part is if-statements in the control flow of update. Missing simple statements like m.subFormFoo = nil can cause a bad loop. When callbacks are configurable things become much easier and everything can stay in the msg/update pattern:

	switch curmsg := msg.(type) {
        case subFormFooCompeted:
                cmd = tea.Batch(cmd, getData(m))
	case getDataSuccess:
		m.subFormBar = NewFormBar()
                cmd = tea.Batch(cmd, subFormBar.Init())
        case subFormBarCompleted:
               // more control flow etc,

Initializing a new form in this way is pretty straight forward

func NewFormBar(m Model) *huh.Form {
	var form *huh.Form
	form = huh.NewForm(
		// cool huh stuff here
	)
	form.CancelCmd = func() tea.Msg {
		return subFormBarCancelled{}
	}
	form.SubmitCmd = func() tea.Msg {
		return subFormBarCompleted{}
	}
	return form
}

Notice that in these cases we aren't using form.Run() to run a form because it's undesirable to have it call tea.Quit when that form is submitted.

For this reason it isn't necessary to use the RunWithCallbacks function I added here. That would be used for top level forms where we want to stay in the "TEA mode" when the form is exited.

@abradley2
Copy link
Contributor Author

abradley2 commented Jun 14, 2024

I plan on making a larger example for this during the weekend once I'm free from work, but curious to know what people think now. I'm on the fence about RunWithCallbacks being necessary for what I'm trying to do here, though I still think it'd be helpfull to have the callback fields exposed either way

@maaslalani
Copy link
Member

I plan on making a larger example for this during the weekend once I'm free from work, but curious to know what people think now. I'm on the fence about RunWithCallbacks being necessary for what I'm trying to do here, though I still think it'd be helpfull to have the callback fields exposed either way

I like exposing the SubmitCmd and CancelCmd. I think we don't need RunWithCallbacks personally. So we should take it out, if we end up having a compelling use case we can add it in another PR.

@abradley2
Copy link
Contributor Author

I plan on making a larger example for this during the weekend once I'm free from work, but curious to know what people think now. I'm on the fence about RunWithCallbacks being necessary for what I'm trying to do here, though I still think it'd be helpfull to have the callback fields exposed either way

I like exposing the SubmitCmd and CancelCmd. I think we don't need RunWithCallbacks personally. So we should take it out, if we end up having a compelling use case we can add it in another PR.

I think you're right. As of now I'm no longer using RunWithCallbacks, so I can't think of a concrete use-case. I've removed it

@maaslalani maaslalani merged commit d7db017 into charmbracelet:main Jun 14, 2024
20 checks passed
renovate bot added a commit to jippi/dottie that referenced this pull request Jul 10, 2024
….mod (#60)

[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [github.com/charmbracelet/huh](https://togithub.com/charmbracelet/huh)
| `v0.4.2` -> `v0.5.1` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fcharmbracelet%2fhuh/v0.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fcharmbracelet%2fhuh/v0.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fcharmbracelet%2fhuh/v0.4.2/v0.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fcharmbracelet%2fhuh/v0.4.2/v0.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>charmbracelet/huh (github.com/charmbracelet/huh)</summary>

###
[`v0.5.1`](https://togithub.com/charmbracelet/huh/compare/v0.5.0...v0.5.1)

[Compare
Source](https://togithub.com/charmbracelet/huh/compare/v0.5.0...v0.5.1)

###
[`v0.5.0`](https://togithub.com/charmbracelet/huh/releases/tag/v0.5.0)

[Compare
Source](https://togithub.com/charmbracelet/huh/compare/v0.4.2...v0.5.0)

### Dynamic Forms 🪄

<img width="600"
src="https://vhs.charm.sh/vhs-6FRmBjNi2aiRb4INPXwIjo.gif" alt="Country /
State form with dynamic inputs running.">

`huh?` forms can now react to changes in other parts of the form.
Replace properties such as `Options`, `Title`, `Description` with their
dynamic counterparts: `OptionsFunc`, `TitleFunc`, and `DescriptionFunc`
to recompute properties values on changes when watched variables change.

Let’s build a simple state / province picker.

```go
var country string
var state string
```

The `country` select will be static, we’ll use this value to recompute
the
options and title for the next input.

```go
huh.NewSelect[string]().
    Options(huh.NewOptions("United States", "Canada", "Mexico")...).
    Value(&country).
    Title("Country").
```

Define your `Select` with `TitleFunc` and `OptionsFunc` and bind them to
the
`&country` value from the previous field. Whenever the user chooses a
different
country, the `TitleFunc` and `OptionsFunc` will be recomputed.

> \[!IMPORTANT]
> We have to pass `&country` as the binding to recompute the function
only when
> `country` changes, otherwise we will hit the API too often.

```go
huh.NewSelect[string]().
    Value(&state).
    Height(8).
    TitleFunc(func() string {
        switch country {
        case "United States":
            return "State"
        case "Canada":
            return "Province"
        default:
            return "Territory"
        }
    }, &country).
    OptionsFunc(func() []huh.Option[string] {
        opts := fetchStatesForCountry(country)
        return huh.NewOptions(opts...)
    }, &country),
```

Lastly, run the `form` with these inputs.

```go
err := form.Run()
if err != nil {
    log.Fatal(err)
}
```

##### Other Changes

#### What's Changed

- Form Layouts by [@&#8203;adamdottv](https://togithub.com/adamdottv) in
[charmbracelet/huh#274
- Resolve conflict between select and filter by
[@&#8203;MikaelFangel](https://togithub.com/MikaelFangel) in
[charmbracelet/huh#252
- Add `CursorText` style by [@&#8203;nervo](https://togithub.com/nervo)
in
[charmbracelet/huh#259
- Adjust input width to char limit by
[@&#8203;nervo](https://togithub.com/nervo) in
[charmbracelet/huh#260
- Implement `WithInput` by
[@&#8203;Delta456](https://togithub.com/Delta456) in
[charmbracelet/huh#271
- Implement WithTimeout by
[@&#8203;Delta456](https://togithub.com/Delta456) in
[charmbracelet/huh#276
- Set filtering state of select by
[@&#8203;PJGaetan](https://togithub.com/PJGaetan) in
[charmbracelet/huh#179
- `Filtering` on `Select` and `MultiSelect` fields by
[@&#8203;maaslalani](https://togithub.com/maaslalani) in
[charmbracelet/huh#283
- Introduce `accessor` by [@&#8203;nervo](https://togithub.com/nervo) in
[charmbracelet/huh#263
- fix: aborting a form returning a timeout error by
[@&#8203;Sculas](https://togithub.com/Sculas) in
[charmbracelet/huh#287

#### New Contributors

- [@&#8203;MikaelFangel](https://togithub.com/MikaelFangel) made their
first contribution in
[charmbracelet/huh#252
- [@&#8203;nervo](https://togithub.com/nervo) made their first
contribution in
[charmbracelet/huh#253
- [@&#8203;shedyfreak](https://togithub.com/shedyfreak) made their first
contribution in
[charmbracelet/huh#230
- [@&#8203;Delta456](https://togithub.com/Delta456) made their first
contribution in
[charmbracelet/huh#271
- [@&#8203;abradley2](https://togithub.com/abradley2) made their first
contribution in
[charmbracelet/huh#280
- [@&#8203;PJGaetan](https://togithub.com/PJGaetan) made their first
contribution in
[charmbracelet/huh#179
- [@&#8203;Sculas](https://togithub.com/Sculas) made their first
contribution in
[charmbracelet/huh#287

**Full Changelog**:
charmbracelet/huh@v0.4.2...v0.5.0

***

<a href="https://charm.sh/"><img alt="The Charm logo"
src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>

Thoughts? Questions? We love hearing from you. Feel free to reach out on
[Twitter](https://twitter.com/charmcli), [The
Fediverse](https://mastodon.technology/@&#8203;charm), or
[Slack](https://charm.sh/slack).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* */8 * * *" (UTC), Automerge - At
any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/jippi/dottie).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MjUuMSIsInVwZGF0ZWRJblZlciI6IjM3LjQyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants